package com.gulimall.seckill.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.gulimall.common.constrant.OrderConstant;
import com.gulimall.common.constrant.SeckillConstant;
import com.gulimall.common.to.SkuInfoTo;
import com.gulimall.common.to.rabbit.SekcillOrderTo;
import com.gulimall.common.utils.R;
import com.gulimall.common.vo.MemberRespVo;
import com.gulimall.seckill.feign.CouponFeignService;
import com.gulimall.seckill.feign.ProductFeignService;
import com.gulimall.seckill.interceptor.SeckillInterceptor;
import com.gulimall.seckill.service.SeckillService;
import com.gulimall.seckill.vo.SeckillSkuSessionVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author kkk
 * @date 2022/8/8
 */
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private CouponFeignService couponFeignService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ProductFeignService productFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    private static final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    private static final String SKUKILL_CHACHE_PREFIX = "seckill:skus";
    private static final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Override
    public void updateSeckillSkuLast3Days() {
        //1.扫描需要参加秒杀的商品
        R r = couponFeignService.seckillSkuList();
        if (r.getResult() == 0) {
            List<JSONObject> sessionItem = JSONObject.parseObject((String) r.get("seckillSessionItem"), List.class);
            List<SeckillSkuSessionVo> skuSessionVos = sessionItem.stream().map(item -> {
                return item.to(SeckillSkuSessionVo.class);
            }).collect(Collectors.toList());

            saveSessionSeckillSku(skuSessionVos);
        } else {
            log.error("查询秒杀商品错误 " + r);
        }
    }

    /**
     * 获取已上架的秒杀商品
     *
     * @return
     */
    @Override
    public List<SeckillSkuSessionVo> getSeckillSkuList() {
        Set<String> keys = stringRedisTemplate.keys(SESSION_CACHE_PREFIX + "*");
        List<SeckillSkuSessionVo> seckillSkuSessionVos = new ArrayList<>();
        if (keys.size() < 1) return seckillSkuSessionVos;

        BoundHashOperations<String, String, String> hashOperations =
                stringRedisTemplate.boundHashOps(SKUKILL_CHACHE_PREFIX);

        return keys.stream().filter(key -> {
            String[] times = key.replace(SESSION_CACHE_PREFIX, "").split("_");
            return Long.parseLong(times[0]) <= System.currentTimeMillis() && Long.parseLong(times[1]) >= System.currentTimeMillis();
        }).map(key -> {
            BoundListOperations<String, String> listOperations = stringRedisTemplate.boundListOps(key);
            List<String> strings = listOperations.range(0, -1);
            List<String> skuLists = hashOperations.multiGet(strings);
            List<SeckillSkuSessionVo.SeckillSkuRelationEntity> relationEntities = skuLists.stream().map(skuJson -> {
                return JSONObject.parseObject(skuJson, SeckillSkuSessionVo.SeckillSkuRelationEntity.class);
            }).collect(Collectors.toList());

            SeckillSkuSessionVo seckillSkuSessionVo = new SeckillSkuSessionVo();
            String[] times = key.replace(SESSION_CACHE_PREFIX, "").split("_");
            seckillSkuSessionVo.setRelationEntities(relationEntities);
            seckillSkuSessionVo.setStartTime(new Date(Long.parseLong(times[0])));
            seckillSkuSessionVo.setEndTime(new Date(Long.parseLong(times[1])));
            return seckillSkuSessionVo;
        }).collect(Collectors.toList());
    }

    @Override
    public SeckillSkuSessionVo.SeckillSkuRelationEntity getSeckillBySkuId(Long skuId) {
        BoundHashOperations<String, String, String> hashOperations =
                stringRedisTemplate.boundHashOps(SKUKILL_CHACHE_PREFIX);

        String str = "\\d_" + skuId;
        for (String key : hashOperations.keys()) {
            if (Pattern.matches(str, key)) {
                String json = hashOperations.get(key);
                SeckillSkuSessionVo.SeckillSkuRelationEntity seckillSkuRelation = JSONObject.parseObject(json,
                        SeckillSkuSessionVo.SeckillSkuRelationEntity.class);

                //随机码
                if (seckillSkuRelation.getStartTime() > System.currentTimeMillis() || seckillSkuRelation.getEndTime() < System.currentTimeMillis()) {
                    seckillSkuRelation.setRandomCode(null);
                }
                return seckillSkuRelation;
            }
        }
        return null;
    }

    /**
     * 秒杀商品
     *
     * @param skuInfo
     * @param key
     * @param count
     * @return
     */
    @Override
    public String seckill(String skuInfo, String key, Integer count) {
        MemberRespVo memberRespVo = SeckillInterceptor.THREAD_LOCAL.get();
        //获取秒杀商品的详细信息
        BoundHashOperations<String, String, String> hashOperations =
                stringRedisTemplate.boundHashOps(SKUKILL_CHACHE_PREFIX);
        String skuJson = hashOperations.get(skuInfo);
        if (!StringUtils.hasText(skuJson)) {
            return null;
        }
        SeckillSkuSessionVo.SeckillSkuRelationEntity skuRelationEntity = JSONObject.parseObject(skuJson,
                SeckillSkuSessionVo.SeckillSkuRelationEntity.class);
        //检验属性合法性
        Long startTime = skuRelationEntity.getStartTime();
        Long endTime = skuRelationEntity.getEndTime();
        long ttl = endTime - startTime;
        //检验时间合法行
        if (startTime > System.currentTimeMillis() || endTime < System.currentTimeMillis()) {
            return null;
        }
        //检验随机码
        if (!key.equals(skuRelationEntity.getRandomCode())) {
            return null;
        }
        //检验商品数量是否合理
        if (count > skuRelationEntity.getSeckillCount().intValue()) {
            return null;
        }
        //检验当前用户是否在秒杀阶段购买过该商品
        String redisKey = memberRespVo.getId() + "_" + skuInfo;
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, count.toString(), ttl,
                TimeUnit.MILLISECONDS);
        if (aBoolean) {
            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + key);
            boolean b = semaphore.tryAcquire();
            if (b) {
                String timeId = IdWorker.getTimeId();
                SekcillOrderTo sekcillOrderTo = new SekcillOrderTo();
                BeanUtils.copyProperties(skuRelationEntity, sekcillOrderTo);
                sekcillOrderTo.setOrderSn(timeId);
                sekcillOrderTo.setCount(count);
                sekcillOrderTo.setMemberId(memberRespVo.getId());
                rabbitTemplate.convertAndSend(OrderConstant.ORDER_EVENT_EXCHANGE, SeckillConstant.SECKILL_ORDER_KEY,
                        sekcillOrderTo);

                return timeId;
            }
        }

        return null;
    }

    private void saveSessionSeckillSku(List<SeckillSkuSessionVo> skuSessionVos) {
        for (SeckillSkuSessionVo skuSessionVo : skuSessionVos) {
            String key =
                    SESSION_CACHE_PREFIX + skuSessionVo.getStartTime().getTime() + "_" + skuSessionVo.getEndTime().getTime();
            if (!stringRedisTemplate.hasKey(key)) {
                List<String> skuIds = skuSessionVo.getRelationEntities().stream().map(item -> {
                    return skuSessionVo.getId() + "_" + item.getSkuId().toString();
                }).collect(Collectors.toList());

                stringRedisTemplate.opsForList().leftPushAll(key, skuIds);
            }
        }

        saveSessionSkuInfo(skuSessionVos);
    }

    private void saveSessionSkuInfo(List<SeckillSkuSessionVo> skuSessionVo) {
        BoundHashOperations<String, Object, Object> hashOperations =
                stringRedisTemplate.boundHashOps(SKUKILL_CHACHE_PREFIX);
        for (SeckillSkuSessionVo seckillSkuSessionVo : skuSessionVo) {
            seckillSkuSessionVo.getRelationEntities().forEach(item -> {
                String key = seckillSkuSessionVo.getId() + "_" + item.getSkuId();
                if (!hashOperations.hasKey(key)) {
                    //缓存商品
                    //1.sku的基本信息
                    R skuInfoResult = productFeignService.skuInfo(item.getSkuId());
                    if (skuInfoResult.getResult() == 0) {
                        String skuInfo = JSONObject.toJSONString(skuInfoResult.get("skuInfo"));
                        String replace = skuInfo.replace("=", ":");
                        SkuInfoTo skuInfoTo = JSONObject.parseObject(replace, SkuInfoTo.class);
                        item.setSkuInfoTo(skuInfoTo);
                    }

                    //设置秒杀商品的时间信息
                    item.setStartTime(seckillSkuSessionVo.getStartTime().getTime());
                    item.setEndTime(seckillSkuSessionVo.getEndTime().getTime());

                    //设置随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    item.setRandomCode(token);

                    //使用库存作为分布式信号量
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                    semaphore.trySetPermits(item.getSeckillCount().intValue());

                    String jsonString = JSONObject.toJSONString(item);
                    hashOperations.put(key, jsonString);
                }
            });
        }
    }
}
